﻿// Necroz project team © <2015> <ArchLord2EMU>
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
// 
// Author:Sagara
// Date:2015-09-06-23:29

using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using ArchLord2Commons.Enums;
using NLog;
using NLog.Config;
using NLog.Targets;

namespace ArchLord2Commons.Utils
{
    public static class Extensions
    {
        public static readonly DateTime StaticDate = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

        /// <summary>
        ///     Global random
        /// </summary>
        public static Random Random { get; } = new Random((int) DateTime.Now.Ticks);

        /// <summary>
        ///     Property, that return current rounded int UTC time
        /// </summary>
        public static int RoundedUtc
        {
            get { return (int) (Utc/1000); }
        }

        public static byte[] GetBytes(string str)
        {
            var bytes = new byte[str.Length*sizeof (char)];
            Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
            return bytes;
        }

        /// <summary>
        ///     Property, that return current long UTC time
        /// </summary>
        public static long Utc
        {
            get { return (long) (DateTime.UtcNow - StaticDate).TotalMilliseconds; }
        }

        public static LoggingConfiguration NLogDefaultConfiguration
        {
            get
            {
                var config = new LoggingConfiguration();

                var consoleTarget = new ColoredConsoleTarget
                {
                    Layout =
                        "${time} | ${message}${onexception:${newline}EXCEPTION OCCURRED${newline}${exception:format=tostring}}",
                    UseDefaultRowHighlightingRules = false
                };
                config.AddTarget("console", consoleTarget);

                var fileTarget = new FileTarget
                {
                    Layout =
                        "${time} | ${message}${onexception:${newline}EXCEPTION OCCURRED${newline}${exception:format=tostring}}",
                    FileName = "${basedir}/Logs/${shortdate}/${level}.txt"
                };
                config.AddTarget("file", fileTarget);

                consoleTarget.RowHighlightingRules.Add(new ConsoleRowHighlightingRule("level == LogLevel.Info",
                    ConsoleOutputColor.Gray,
                    ConsoleOutputColor.Black));
                consoleTarget.RowHighlightingRules.Add(new ConsoleRowHighlightingRule("level == LogLevel.Warn",
                    ConsoleOutputColor.Yellow,
                    ConsoleOutputColor.Black));
                consoleTarget.RowHighlightingRules.Add(new ConsoleRowHighlightingRule("level == LogLevel.Error",
                    ConsoleOutputColor.Red,
                    ConsoleOutputColor.Black));
                consoleTarget.RowHighlightingRules.Add(new ConsoleRowHighlightingRule("level == LogLevel.Fatal",
                    ConsoleOutputColor.Red,
                    ConsoleOutputColor.White));

                var rule1 = new LoggingRule("*", LogLevel.Debug, consoleTarget);
                config.LoggingRules.Add(rule1);

                var rule2 = new LoggingRule("*", LogLevel.Debug, fileTarget);
                config.LoggingRules.Add(rule2);

                return config;
            }
        }

        private static readonly string[] Baths;

        static Extensions()
        {
            Baths = new string[256];
            for (int i = 0; i < 256; i++)
                Baths[i] = String.Format("{0:X2}", i);
        }

        public static string ToHex(this byte[] array)
        {
            var builder = new StringBuilder(array.Length*2);

            foreach (byte t in array)
                builder.Append(Baths[t]);

            return builder.ToString();
        }

        public static string FormatHex(this byte[] data)
        {
            var builder = new StringBuilder(data.Length*4);

            int count = 0;
            int pass = 1;
            foreach (byte b in data)
            {
                if (count == 0)
                    builder.AppendFormat("{0,-6}\t", "[" + (pass - 1)*16 + "]");

                count++;
                builder.Append(b.ToString("X2"));
                if (count == 4 || count == 8 || count == 12)
                    builder.Append(" ");
                if (count == 16)
                {
                    builder.Append("\t");
                    for (int i = (pass*count) - 16; i < (pass*count); i++)
                    {
                        var c = (char) data[i];
                        if (c > 0x1f && c < 0x80)
                            builder.Append(c);
                        else
                            builder.Append(".");
                    }
                    builder.Append("\r\n");
                    count = 0;
                    pass++;
                }
            }

            return builder.ToString();
        }

        public static byte[] ToBytes(this String hexString)
        {
            try
            {
                var result = new byte[hexString.Length/2];

                for (int index = 0; index < result.Length; index++)
                {
                    string byteValue = hexString.Substring(index*2, 2);
                    result[index] = Byte.Parse(byteValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture);
                }

                return result;
            }
            catch (Exception)
            {
                Console.WriteLine("Invalid hex string: {0}", hexString);
                throw;
            }
        }

        /// <summary>
        /// Read null-terminated unicode string from binaryreader
        /// </summary>
        /// <returns></returns>
        public static string ReadNtString(this BinaryReader br)
        {
            var sb = new StringBuilder();

            short ch;
            while (br.BaseStream.Position < br.BaseStream.Length && (ch = br.ReadInt16()) != 0)
                sb.Append((char) ch);

            return sb.ToString();
        }

        /// <summary>
        /// Skip data in current stream
        /// </summary>
        /// <param name="br"></param>
        /// <param name="count">len</param>
        /// <returns></returns>
        public static void Skip(this BinaryReader br, int count)
        {
            br.BaseStream.Position += count;
        }

        /// <summary>
        /// Skip data in current stream
        /// </summary>
        /// <param name="br"></param>
        /// <param name="count">len</param>
        /// <returns></returns>
        public static void Skip(this BinaryWriter br, int count)
        {
            br.BaseStream.Position += count;
        }

        /// <summary>
        /// Write null-terminated string to target reader
        /// Default encoding - unicode
        /// </summary>
        /// <param name="wr"></param>
        /// <param name="val"></param>
        /// <param name="enc"></param>
        public static void WriteNtString(this BinaryWriter wr, string val, Encoding enc = null)
        {
            if (enc == null)
                enc = Encoding.Unicode;

            wr.Write(enc.GetBytes(val));
            wr.Write((short) 0);
        }

        /// <summary>
        /// Read string with fixed len from current reader
        /// </summary>
        /// <param name="br"></param>
        /// <param name="len"></param>
        /// <param name="enc">encoding, unicode by default</param>
        /// <returns></returns>
        public static string ReadString(this BinaryReader br, int len, Encoding enc = null)
        {
            if (enc == null)
                enc = Encoding.Unicode;

            return enc.GetString(br.ReadBytes(len));
        }

        /// <summary>
        /// Write string with padding on current writer
        /// </summary>
        /// <param name="writer">Writer</param>
        /// <param name="text"></param>
        /// <param name="padding"></param>
        public static void WriteS(this BinaryWriter writer, string text, int padding = 0)
        {
            if (text == null)
                writer.Write((short)0);
            
            else
            {
                var encoding = Encoding.ASCII;

                writer.Write(encoding.GetBytes(text));
                //writer.Write((byte)0);
                //writer.Write((short)0);
            }

            if (padding > 0)
            {
                var encoding = Encoding.ASCII;
                if (text == null) return;

                for (var x = encoding.GetBytes(text).Length; x < padding; x++)
                    writer.Write((byte)0);
            }
        }
        public static void WriteSs(this BinaryWriter writer, string text, int padding = 0)
        {
            if (text == null)
                writer.Write((short)0);

            else
            {
                var encoding = Encoding.Unicode;

                writer.Write(encoding.GetBytes(text));
            }

            if (padding > 0)
            {
                var encoding = Encoding.Unicode;
                if (text == null) return;

                for (var x = encoding.GetBytes(text).Length; x < padding; x++)
                    writer.Write((byte)0);
            }
        }

        public static byte GetFractionByClass(EClassType classType)
        {
            switch (classType)
            {
               case EClassType.Slayer:
               case EClassType.Tank:
               case EClassType.Arbalist:
               case EClassType.Magic:
               case EClassType.Pistol:
                    return 0;

               case EClassType.Axe:
               case EClassType.Lance:
               case EClassType.Bow:
               case EClassType.Wand:
               case EClassType.Orb:
                    return 1;

                default:
                    throw new Exception("Class type not registered!");
            }
        }


        public static byte GetFractionByRace(ERace race)
        {
            switch (race)
            {
               case ERace.Human:
               case ERace.DragonScion:
                    return 0;

                case ERace.Orc:
                case ERace.MoonElf:
                    return 1;

                default:
                    throw new Exception("Race type not registered!");
            }
        }
        /// <summary>
        /// Read byte array and return hex representation of it
        /// </summary>
        /// <param name="br"></param>
        /// <param name="len"></param>
        /// <returns></returns>
        public static string ReadHex(this BinaryReader br, int len)
        {
            return br.ReadBytes(len).ToHex();
        }

        public static int ToInt(this string addr)
        {
#pragma warning disable 618
            return IPAddress.NetworkToHostOrder((int) IPAddress.Parse(addr).Address);
#pragma warning restore 618
        }

        public static string ReverseIp(this int ip)
        {
            return BitConverter.GetBytes(ip).Reverse().ToArray().ToHex();
        }

        public static long ShuffleNumber()
        {
            while (true)
            {
                const string original = "0123456789";

                var num = new Random();

                var rand = new string(original.ToCharArray().OrderBy(s => (num.Next(2)%2) == 0).ToArray());

                if (Convert.ToInt64(rand) < 1000000000)
                    continue;

                if (Convert.ToInt64(rand) > long.MaxValue) continue;
                return Convert.ToInt64(rand);
            }
        }
        public static string CalculateMd5Hash(this string input)
        {
            var md5Hasher = MD5.Create();
            var data = md5Hasher.ComputeHash(Encoding.Default.GetBytes(input));
            var sBuilder = new StringBuilder();

            foreach (byte t in data)
                sBuilder.Append(t.ToString("x2"));

            return sBuilder.ToString();
        }
    }
}
